package util

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gomodule/redigo/redis"
	"mall-api-gin/pkg/e"
	"mall-api-gin/pkg/gredis"
	"mall-api-gin/pkg/logging"
	"strconv"
	"time"

	"github.com/dgrijalva/jwt-go"
)

var jwtSecret []byte

type Claims struct {
	Username string `json:"username"`
	Uuid     string `json:"uuid"`
	Uid      int    `json:"uid"`
	State    int    `json:"state"`
	jwt.StandardClaims
}

type CustomerClaims struct {
	Username     string `json:"username"`
	Uuid         string `json:"uuid"`
	Uid          int    `json:"uid"`
	State        int    `json:"state"`
	AuthorNumber string `json:"author_number"` // 客户端授权id
	Team         int    `json:"team"`
	jwt.StandardClaims
}

// GenerateToken generate tokens used for auth
func GenerateToken(username string, uid int) (string, error) {
	nowTime := time.Now()
	expireTime := nowTime.Add(e.AdminTokenExpireTime * time.Second)
	var state int
	userTokenState := fmt.Sprintf(e.UserLoginTokenState, username)
	reply, err := gredis.Get(userTokenState)
	fmt.Printf("userTokenState = %v reply = %v, err = %v\n", userTokenState, reply, err)
	if err != nil {
		state = 1
	} else {
		reply, _ := strconv.Atoi(string(reply))
		state = reply + 1
	}
	if err := gredis.Set(userTokenState, state, e.AdminTokenExpireTime); err != nil {
		logging.Error(err)
	}
	uuid := GetUUID()
	claims := Claims{
		username,
		uuid,
		uid,
		state,
		jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			Issuer:    "gin-blog",
		},
	}

	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := tokenClaims.SignedString(jwtSecret)

	return token, err
}

// ParseToken parsing token
func ParseToken(token string) (*Claims, error) {
	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})

	if tokenClaims != nil {
		if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
			reply, err := gredis.Get(fmt.Sprintf(e.UserLoginTokenState, claims.Username))
			if err == nil {
				reply, _ := strconv.Atoi(string(reply))
				if reply != claims.State {
					return nil, jwt.NewValidationError("token not use", jwt.ValidationErrorMalformed)
				}
				return claims, nil
			}
			return nil, jwt.NewValidationError("token not use", jwt.ValidationErrorMalformed)
		}
	}

	return nil, err
}

// 客户端用户生成token
func GenerateCustomerToken(username, authorNumber, clientIp string, team, uid int) (string, error) {
	nowTime := time.Now()
	expireTime := nowTime.Add(e.CustomerTokenExpireTime * time.Second)

	loginCacheData, err := CustomerLoginJwt.SetCustomerLoginCache(username, authorNumber, clientIp)
	if err != nil {
		return "", err
	}

	uuid := GetUUID()
	claims := CustomerClaims{
		username,
		uuid,
		uid,
		loginCacheData.LoginState,
		authorNumber,
		team,
		jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			Issuer:    "gin-blog",
		},
	}

	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := tokenClaims.SignedString(jwtSecret)

	return token, err
}

// 解析客户端用户token
func ParseCustomerToken(token string) (string, *CustomerClaims, error) {
	tokenClaims, err := jwt.ParseWithClaims(token, &CustomerClaims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})
	username := ""
	if tokenClaims != nil {
		if claims, ok := tokenClaims.Claims.(*CustomerClaims); ok && tokenClaims.Valid {
			username = claims.Username
			loginCacheInfo, _ := CustomerLoginJwt.GetCustomerLoginCache(claims.Username)
			if err == nil {
				if claims.Team != e.PublicGroup {
					if loginCacheInfo.LoginState != claims.State {
						return username, nil, jwt.NewValidationError("token not use", jwt.ValidationErrorMalformed)
					}
				}
				return username, claims, nil
			}
			return username, nil, jwt.NewValidationError("token not use", jwt.ValidationErrorMalformed)
		}
	}

	return username, nil, err
}

// 刷新用户token
func RefreshCustomerToken(c *gin.Context) (string, error) {

	_, claims, err := ParseCustomerToken(c.GetHeader("Authorization"))
	if err != nil {
		return "", err
	}
	uuid := claims.Uuid
	uid := claims.Uid
	username := claims.Username
	c.Set(e.UserUuidKey, uuid)
	c.Set(e.UserIdKey, uid)
	c.Set(e.UserNameKey, username)
	c.Set(e.UserTeamKey, claims.Team)
	token, err := GenerateCustomerToken(claims.Username, claims.AuthorNumber, c.ClientIP(), claims.Team, claims.Uid)
	return token, err
}

type CustomerLoginCacheInfo struct {
	AuthorNumber string `json:"author_number"` // 客户端授权编号
	LoginState   int    `json:"login_state"`   // 登录计数
	ClientIp     string `json:"client_ip"`
}

type customerLoginJwt struct{}

var CustomerLoginJwt = new(customerLoginJwt)

// SetCustomerLoginCache : 设置用户登录信息缓存
func (c *customerLoginJwt) SetCustomerLoginCache(username, authorNumber, clientIp string) (customerLoginInfo *CustomerLoginCacheInfo, err error) {

	userTokenState := fmt.Sprintf(e.CustomerTokenState, username)

	customerLoginInfo, err = c.GetCustomerLoginCache(username)
	if err != nil {
		return
	}

	if customerLoginInfo.LoginState == 0 {
		customerLoginInfo.LoginState = 1
		customerLoginInfo.AuthorNumber = authorNumber
		customerLoginInfo.ClientIp = clientIp
	} else {
		customerLoginInfo.LoginState += 1
	}
	err = gredis.Set(userTokenState, customerLoginInfo, e.CustomerTokenExpireTime)
	return
}

// GetCustomerLoginCache : 获取缓存中用户登录信息
func (c *customerLoginJwt) GetCustomerLoginCache(username string) (customerLoginInfo *CustomerLoginCacheInfo, err error) {

	customerLoginInfo = &CustomerLoginCacheInfo{}
	userTokenState := fmt.Sprintf(e.CustomerTokenState, username)
	cacheInfo, err := gredis.Get(userTokenState)
	if err != nil {
		if err != redis.ErrNil {
			return
		}
		err = nil
		return
	}
	err = json.Unmarshal(cacheInfo, customerLoginInfo)
	return
}
